import { BlockFactory } from "./block";
import { Matrix } from "./matrix";
import { Timer } from "./timer";

const WIDTH = 10;
const HEIGHT = 18;
const WAIT = 500;

function createMatrixRow(width = WIDTH) {
    return Array.apply(null, { length: width });
}

function createMatrix(width = WIDTH, height = HEIGHT) {
    return Array.apply(null, { length: height }).map(() => {
        return createMatrixRow(width);
    });
}

export class Puzzle {
    static create() {
        return new Puzzle();
    }

    constructor() {
        this.factory = BlockFactory.default;
        this.puzzle = new Matrix(WIDTH, HEIGHT);
        this.preview = new Matrix(4, 4);
        this.matrix = createMatrix();
        this.timer = new Timer(this.process.bind(this), WAIT);
    }

    get next() {
        return this._next;
    }

    set next(value) {
        this._next = value;
        this.preview.render(value.fasten());
    }

    run() {
        this.puzzle.build($("#puzzle"));
        this.preview.build($("#preview"));
        this.next = this.factory.create();

        this.setEvents();
        this.process();
    }

    restart() {
        this.matrix = createMatrix(WIDTH, HEIGHT);
        this.render();
        this.current = null;
        this.process();
    }

    process() {
        if (!this.current) {
            this.nextShape();
        } else {
            this.fallDown();
        }
    }

    nextShape() {
        const block = this.current = this.next;
        this.next = this.factory.create();
        this.current.setPosition(~~((WIDTH - block.width) / 2), -block.height);
        this.fallDown();
        this.timer.start();
    }

    fallDown() {
        const current = this.current;
        const blocks = current.fastenOffset(0, 1);
        if (!this.isPutable(blocks)) {
            this.fastenCurrent();
            return;
        }

        current.moveDown();
        this.render();
    }

    fastenCurrent() {
        this.timer.stop();

        if (this.current.top < 0) {
            this.restart();
            return;
        }

        this.current.fasten().forEach(block => {
            this.matrix[block.y][block.x] = block;
        });
        this.current = null;
        this.eraseRows();
    }

    eraseRows() {
        const fulls = [];
        const matrix = this.matrix;
        matrix.forEach((row, i) => {
            if (row.length && row.every(t => t)) {
                fulls.push(i);
            }
        });
        if (!fulls.length) {
            this.process();
            return;
        }

        const reduceRows = () => {
            const matrix = this.matrix;
            let newMatrix = createMatrix(void 0, fulls.length);
            let next = 0;
            matrix.forEach((row, i) => {
                if (i === fulls[next]) {
                    next++;
                    if (next >= fulls.length) {
                        next = -1;
                    }
                } else {
                    row.filter(t => t).forEach(t => t.y = newMatrix.length);
                    newMatrix.push(row);
                }
            });

            this.matrix = newMatrix;
        };

        let columnIndex = 0;
        const t = setInterval(() => {
            fulls.forEach((rowIndex) => {
                matrix[rowIndex][columnIndex] = null;
                this.render();
            });
            if (++columnIndex >= this.puzzle.width) {
                clearInterval(t);
                reduceRows();
                this.render();
                this.process();
            }
        }, 10);
    }

    render() {
        let data = this.current ? this.current.fasten() : [];
        data = data.concat(this.matrix.reduce((all, row) => {
            return all.concat(row.filter(t => t));
        }, []));
        this.puzzle.render(data);
    }

    isPutable(list) {
        const height = this.puzzle.height;
        const width = this.puzzle.width;

        return list.every(t => {
            if (t.x < 0 || t.x >= width || t.y < 0 || t.y >= height) {
                return false;
            }

            return !this.matrix[t.y][t.x];
        });
    }

    setEvents() {
        const puzzle = this.puzzle;

        const keyDownHandlers = {
            // up
            "38": () => {
                const current = this.current;
                if (!current || !this.isPutable(current.fastenRotate(puzzle.width, puzzle.height))) {
                    return;
                }

                current.rotate(puzzle.width, puzzle.height);
                this.render();
            },

            // left
            "37": () => {
                const current = this.current;
                if (!current || !this.isPutable(current.fastenOffset(-1, 0))) {
                    return;
                }

                this.current.moveLeft();
                this.render();
            },

            // right
            "39": () => {
                const current = this.current;
                if (!current || !this.isPutable(current.fastenOffset(1, 0))) {
                    return;
                }

                current.moveRight(puzzle.width);
                this.render();
            },

            // down
            "40": () => {
                const current = this.current;
                if (!current || !this.isPutable(current.fastenOffset(0, 1))) {
                    return;
                }

                current.moveDown(puzzle.height);
                this.render();
            },

            // fall down to bottom
            "32": () => {
                const current = this.current;
                if (!current) {
                    return;
                }

                const left = current.left;
                const height = this.puzzle.height;
                console.log(left, height);
                for (let top = Math.max(current.top + 1, 0); top < height; top++) {
                    if (!this.isPutable(current.fasten(left, top))) {
                        console.log(left, top);
                        current.setPosition(left, top - 1);
                        break;
                    }
                }

                console.log(current.position);
                this.fastenCurrent();
            }
        };

        $(document).on("keydown", e => {
            const handler = keyDownHandlers[e.keyCode];
            if (handler) {
                handler();
            }
        });
    }
}
